/*
 * Copyright (c) 2012 Jason Polites
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.isunland.managesystem.widget;

import android.content.res.Configuration;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;

@SuppressWarnings("all") public class GestureImageViewTouchListener implements OnTouchListener {

  private GestureImageView image;
  private OnClickListener onClickListener;

  private final PointF current = new PointF();
  private final PointF last = new PointF();
  private final PointF next = new PointF();
  private final PointF midpoint = new PointF();

  private final VectorF scaleVector = new VectorF();
  private final VectorF pinchVector = new VectorF();

  private boolean touched = false;
  private boolean inZoom = false;

  private float initialDistance;
  private float lastScale = 1.0f;
  private float currentScale = 1.0f;

  private float boundaryLeft = 0;
  private float boundaryTop = 0;
  private float boundaryRight = 0;
  private float boundaryBottom = 0;

  private float maxScale = 5.0f;
  private float minScale = 0.25f;
  private float fitScaleHorizontal = 1.0f;
  private float fitScaleVertical = 1.0f;

  private int canvasWidth = 0;
  private int canvasHeight = 0;

  private float centerX = 0;
  private float centerY = 0;

  private float startingScale = 0;

  private boolean canDragX = false;
  private boolean canDragY = false;

  private boolean multiTouch = false;

  private int displayWidth;
  private int displayHeight;

  private int imageWidth;
  private int imageHeight;

  private FlingListener flingListener;
  private FlingAnimation flingAnimation;
  private ZoomAnimation zoomAnimation;
  private MoveAnimation moveAnimation;
  private GestureDetector tapDetector;
  private GestureDetector flingDetector;
  private GestureImageViewListener imageListener;

  public GestureImageViewTouchListener(final GestureImageView image, int displayWidth,
      int displayHeight) {
    super();

    this.image = image;

    this.displayWidth = displayWidth;
    this.displayHeight = displayHeight;

    this.centerX = (float) displayWidth / 2.0f;
    this.centerY = (float) displayHeight / 2.0f;

    this.imageWidth = image.getImageWidth();
    this.imageHeight = image.getImageHeight();

    startingScale = image.getScale();

    currentScale = startingScale;
    lastScale = startingScale;

    boundaryRight = displayWidth;
    boundaryBottom = displayHeight;
    boundaryLeft = 0;
    boundaryTop = 0;

    next.x = image.getImageX();
    next.y = image.getImageY();

    flingListener = new FlingListener();
    flingAnimation = new FlingAnimation();
    zoomAnimation = new ZoomAnimation();
    moveAnimation = new MoveAnimation();

    flingAnimation.setListener(new FlingAnimationListener() {
      @Override public void onMove(float x, float y) {
        handleDrag(current.x + x, current.y + y);
      }

      @Override public void onComplete() {
      }
    });

    zoomAnimation.setZoom(2.0f);
    zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() {
      @Override public void onZoom(float scale, float x, float y) {
        if (scale <= maxScale && scale >= minScale) {
          handleScale(scale, x, y);
        }
      }

      @Override public void onComplete() {
        inZoom = false;
        handleUp();
      }
    });

    moveAnimation.setMoveAnimationListener(new MoveAnimationListener() {

      @Override public void onMove(float x, float y) {
        image.setPosition(x, y);
        image.redraw();
      }
    });

    tapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() {
      @Override public boolean onDoubleTap(MotionEvent e) {
        startZoom(e);
        return true;
      }

      @Override public boolean onSingleTapConfirmed(MotionEvent e) {
        if (!inZoom) {
          if (onClickListener != null) {
            onClickListener.onClick(image);
            return true;
          }
        }

        return false;
      }
    });

    flingDetector = new GestureDetector(image.getContext(), flingListener);
    imageListener = image.getGestureImageViewListener();

    calculateBoundaries();
  }

  private void startFling() {
    flingAnimation.setVelocityX(flingListener.getVelocityX());
    flingAnimation.setVelocityY(flingListener.getVelocityY());
    image.animationStart(flingAnimation);
  }

  private void startZoom(MotionEvent e) {
    inZoom = true;
    zoomAnimation.reset();

    float zoomTo;

    if (image.isLandscape()) {
      if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
        int scaledHeight = image.getScaledHeight();

        if (scaledHeight < canvasHeight) {
          zoomTo = fitScaleVertical / currentScale;
          zoomAnimation.setTouchX(e.getX());
          zoomAnimation.setTouchY(image.getCenterY());
        } else {
          zoomTo = fitScaleHorizontal / currentScale;
          zoomAnimation.setTouchX(image.getCenterX());
          zoomAnimation.setTouchY(image.getCenterY());
        }
      } else {
        int scaledWidth = image.getScaledWidth();

        if (scaledWidth == canvasWidth) {
          zoomTo = currentScale * 4.0f;
          zoomAnimation.setTouchX(e.getX());
          zoomAnimation.setTouchY(e.getY());
        } else if (scaledWidth < canvasWidth) {
          zoomTo = fitScaleHorizontal / currentScale;
          zoomAnimation.setTouchX(image.getCenterX());
          zoomAnimation.setTouchY(e.getY());
        } else {
          zoomTo = fitScaleHorizontal / currentScale;
          zoomAnimation.setTouchX(image.getCenterX());
          zoomAnimation.setTouchY(image.getCenterY());
        }
      }
    } else {
      if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {

        int scaledHeight = image.getScaledHeight();

        if (scaledHeight == canvasHeight) {
          zoomTo = currentScale * 4.0f;
          zoomAnimation.setTouchX(e.getX());
          zoomAnimation.setTouchY(e.getY());
        } else if (scaledHeight < canvasHeight) {
          zoomTo = fitScaleVertical / currentScale;
          zoomAnimation.setTouchX(e.getX());
          zoomAnimation.setTouchY(image.getCenterY());
        } else {
          zoomTo = fitScaleVertical / currentScale;
          zoomAnimation.setTouchX(image.getCenterX());
          zoomAnimation.setTouchY(image.getCenterY());
        }
      } else {
        int scaledWidth = image.getScaledWidth();

        if (scaledWidth < canvasWidth) {
          zoomTo = fitScaleHorizontal / currentScale;
          zoomAnimation.setTouchX(image.getCenterX());
          zoomAnimation.setTouchY(e.getY());
        } else {
          zoomTo = fitScaleVertical / currentScale;
          zoomAnimation.setTouchX(image.getCenterX());
          zoomAnimation.setTouchY(image.getCenterY());
        }
      }
    }

    zoomAnimation.setZoom(zoomTo);
    image.animationStart(zoomAnimation);
  }

  private void stopAnimations() {
    image.animationStop();
  }

  @Override public boolean onTouch(View v, MotionEvent event) {

    if (!inZoom) {

      if (!tapDetector.onTouchEvent(event)) {
        if (event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) {
          startFling();
        }

        if (event.getAction() == MotionEvent.ACTION_UP) {
          handleUp();
        } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
          stopAnimations();

          last.x = event.getX();
          last.y = event.getY();

          if (imageListener != null) {
            imageListener.onTouch(last.x, last.y);
          }

          touched = true;
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
          if (event.getPointerCount() > 1) {
            multiTouch = true;
            if (initialDistance > 0) {

              pinchVector.set(event);
              pinchVector.calculateLength();

              float distance = pinchVector.length;

              if (initialDistance != distance) {

                float newScale = (distance / initialDistance) * lastScale;

                if (newScale <= maxScale) {
                  scaleVector.length *= newScale;

                  scaleVector.calculateEndPoint();

                  scaleVector.length /= newScale;

                  float newX = scaleVector.end.x;
                  float newY = scaleVector.end.y;

                  handleScale(newScale, newX, newY);
                }
              }
            } else {
              initialDistance = MathUtils.distance(event);

              MathUtils.midpoint(event, midpoint);

              scaleVector.setStart(midpoint);
              scaleVector.setEnd(next);

              scaleVector.calculateLength();
              scaleVector.calculateAngle();

              scaleVector.length /= lastScale;
            }
          } else {
            if (!touched) {
              touched = true;
              last.x = event.getX();
              last.y = event.getY();
              next.x = image.getImageX();
              next.y = image.getImageY();
            } else if (!multiTouch) {
              if (handleDrag(event.getX(), event.getY())) {
                image.redraw();
              }
            }
          }
        }
      }
    }

    return true;
  }

  protected void handleUp() {

    multiTouch = false;

    initialDistance = 0;
    lastScale = currentScale;

    if (!canDragX) {
      next.x = centerX;
    }

    if (!canDragY) {
      next.y = centerY;
    }

    boundCoordinates();

    if (!canDragX && !canDragY) {

      if (image.isLandscape()) {
        currentScale = fitScaleHorizontal;
        lastScale = fitScaleHorizontal;
      } else {
        currentScale = fitScaleVertical;
        lastScale = fitScaleVertical;
      }
    }

    image.setScale(currentScale);
    image.setPosition(next.x, next.y);

    if (imageListener != null) {
      imageListener.onScale(currentScale);
      imageListener.onPosition(next.x, next.y);
    }

    image.redraw();
  }

  protected void handleScale(float scale, float x, float y) {

    currentScale = scale;

    if (currentScale > maxScale) {
      currentScale = maxScale;
    } else if (currentScale < minScale) {
      currentScale = minScale;
    } else {
      next.x = x;
      next.y = y;
    }

    calculateBoundaries();

    image.setScale(currentScale);
    image.setPosition(next.x, next.y);

    if (imageListener != null) {
      imageListener.onScale(currentScale);
      imageListener.onPosition(next.x, next.y);
    }

    image.redraw();
  }

  protected boolean handleDrag(float x, float y) {
    current.x = x;
    current.y = y;

    float diffX = (current.x - last.x);
    float diffY = (current.y - last.y);

    if (diffX != 0 || diffY != 0) {

      if (canDragX) next.x += diffX;
      if (canDragY) next.y += diffY;

      boundCoordinates();

      last.x = current.x;
      last.y = current.y;

      if (canDragX || canDragY) {
        image.setPosition(next.x, next.y);

        if (imageListener != null) {
          imageListener.onPosition(next.x, next.y);
        }

        return true;
      }
    }

    return false;
  }

  public void reset() {
    currentScale = startingScale;
    next.x = centerX;
    next.y = centerY;
    calculateBoundaries();
    image.setScale(currentScale);
    image.setPosition(next.x, next.y);
    image.redraw();
  }

  public float getMaxScale() {
    return maxScale;
  }

  public void setMaxScale(float maxScale) {
    this.maxScale = maxScale;
  }

  public float getMinScale() {
    return minScale;
  }

  public void setMinScale(float minScale) {
    this.minScale = minScale;
  }

  public void setOnClickListener(OnClickListener onClickListener) {
    this.onClickListener = onClickListener;
  }

  protected void setCanvasWidth(int canvasWidth) {
    this.canvasWidth = canvasWidth;
  }

  protected void setCanvasHeight(int canvasHeight) {
    this.canvasHeight = canvasHeight;
  }

  protected void setFitScaleHorizontal(float fitScale) {
    this.fitScaleHorizontal = fitScale;
  }

  protected void setFitScaleVertical(float fitScaleVertical) {
    this.fitScaleVertical = fitScaleVertical;
  }

  protected void boundCoordinates() {
    if (next.x < boundaryLeft) {
      next.x = boundaryLeft;
    } else if (next.x > boundaryRight) {
      next.x = boundaryRight;
    }

    if (next.y < boundaryTop) {
      next.y = boundaryTop;
    } else if (next.y > boundaryBottom) {
      next.y = boundaryBottom;
    }
  }

  protected void calculateBoundaries() {

    int effectiveWidth = Math.round((float) imageWidth * currentScale);
    int effectiveHeight = Math.round((float) imageHeight * currentScale);

    canDragX = effectiveWidth > displayWidth;
    canDragY = effectiveHeight > displayHeight;

    if (canDragX) {
      float diff = (float) (effectiveWidth - displayWidth) / 2.0f;
      boundaryLeft = centerX - diff;
      boundaryRight = centerX + diff;
    }

    if (canDragY) {
      float diff = (float) (effectiveHeight - displayHeight) / 2.0f;
      boundaryTop = centerY - diff;
      boundaryBottom = centerY + diff;
    }
  }
}
